# Copyright (c) 2011 Peter Shinners

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.



import os
import sys

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import unittest
from minivec import Mat, Vec
import minivec


class TestMatrix(unittest.TestCase):
    def setUp(self):
        minivec.ResetError()
        minivec.ResetEpsilon()

        
    def TestConstructors(self):
        "Various forms of constructors"
        zero = Mat(0.0)
        one = Mat(1.0)
        def counter():
            for i in range(16):
                yield i * 1.22
        numbers = dict.fromkeys(range(1, 17))
        matrices = [
            Mat(),  # 0
            zero, # 1
            one,  # 2
            Mat(1.1, 1.2, 1.3, 1.4,  2.1, 2.2, 2.3, 2.4,  3.1, 3.2, 3.3, 3.4,  4.1, 4.2, 4.3, 4.4),  # 3
            Mat((1.1, 1.2, 1.3, 1.4,  2.1, 2.2, 2.3, 2.4,  3.1, 3.2, 3.3, 3.4,  4.1, 4.2, 4.3, 4.4)),  # 4
            Mat(one),  # 5
            Mat(counter()),  # 6
            Mat(numbers),  # 7
            Mat((0, 1, 2, 3), xrange(4), [3, 2, 1, 0], (-5, -6, -7, -8)),  # 8
            Mat(range(1, 17)),  # 9
            Mat(xrange(1, 17)),  # 10
        ]
        tuples = map(tuple, matrices)

        self.assertEqual(tuples[0], tuples[2])
        self.assertEqual(tuples[3], tuples[4])
        self.assertEqual(tuples[2], tuples[5])
        self.assertEqual(tuples[9], tuples[10])
        self.assertEqual(tuples[6], tuple(counter()))
        self.assertEqual(tuples[7], tuple(numbers))
        self.assertEqual(type(tuples[8][0]), float)


    def TestBadConstructors(self):
        "Constructors that should fail"
        self.assertRaises(TypeError, lambda: Mat("vector"))
        self.assertRaises(TypeError, lambda: Mat("1.0"))
        self.assertRaises(TypeError, lambda: Mat((1, 2, 3, 4)))
        self.assertRaises(TypeError, lambda: Mat((1, 2, 3, 4, 5)))
        self.assertRaises(TypeError, lambda: Mat((1, 2)))
        self.assertRaises(TypeError, lambda: Mat((0, 1, 2, 3), xrange(4), [3, 1], (-5, -6, -7, -8)))
        self.assertRaises(TypeError, lambda: Mat((0, 1, 2, 3), xrange(4), 3, (-5, -6, -7, -8)))

        
    def TestTransforms(self):
        "Simplest forms of transform creation"
        ident = Mat()
        self.assertEqual(ident.translate(1.1, 2.2, 3.3), Mat.Translate(1.1, 2.2, 3.3))
        self.assertEqual(ident.rotate(1.1, 2.2, 3.3), Mat.Rotate(1.1, 2.2, 3.3))
        self.assertEqual(ident.scale(1.1, 2.2, 3.3), Mat.Scale(1.1, 2.2, 3.3))
        self.assertEqual(ident.shear(1.1, 2.2, 3.3), Mat.Shear(1.1, 2.2, 3.3))
        self.assertEqual(ident.rotateAxis(44.4, (1.1, 2.2, 3.3)), Mat.RotateAxis(44.4, (1.1, 2.2, 3.3)))

        trans = Mat.Translate(-152.75, 19.6, 23.2)
        transVal = (1, 0, 0, 0,  0, 1, 0, 0,  0, 0, 1, 0,  -152.75, 19.6, 23.2, 1)
        self.assertEqual(trans, transVal)

        rot = Mat.Rotate(39.4, -7.7, 87.1)
        rotVal = (0.0501367536, 0.9897141042, 0.1339861854, 0.0, -0.7760466605, -0.0458413444, 0.6290072749, 0.0, 0.6286794785, -0.1355159145, 0.7657659892, 0.0, 0.0, 0.0, 0.0, 1.0)
        self.assertAlmostMat(rot, rotVal)

        shear = Mat.Shear(1.05, 6.1, -2.9)
        shearVal = (1, 0, 0, 0,  1.05, 1, 0, 0,  6.1, -2.9, 1, 0,  0, 0, 0, 1)
        self.assertEqual(shear, shearVal)

        scale = Mat.Scale(22, -.00032, 3.7)
        scaleVal= (22.0, 0, 0, 0,  0, -.00032, 0, 0,  0, 0, 3.7, 0,  0, 0, 0, 1)
        self.assertEqual(scale, scaleVal)

        axis = Mat.RotateAxis(30.0, (.4, -.7, .1))

        minivec.PushEpsilon(1e-6)
        same = Mat.Rotate(14.96174, -26.036262, 0.30022)
        self.assertAlmostMat(same, axis)

    def TestMultiplication(self):
        a = Mat.Translate(5, 10, 20)
        b = Mat.Translate(-40, 8, 12)
        self.assertEqual(a * b, b * a)

        minivec.PushEpsilon(1e-6)
        trans = Mat(1, 0, 0, 0,  0, 1, 0, 0,  0, 0, 1, 0,  -152.75, 19.6, 23.2, 1)
        rot = Mat(0.0501367536, 0.9897141042, 0.1339861854, 0.0, -0.7760466605, -0.0458413444, 0.6290072749, 0.0, 0.6286794785, -0.1355159145, 0.7657659892, 0.0, 0.0, 0.0, 0.0, 1.0)
        scale = Mat(22.0, 0, 0, 0,  0, -.00032, 0, 0,  0, 0, 3.7, 0,  0, 0, 0, 1)
        shear = Mat(1, 0, 0, 0, 1.05, 1, 0, 0, 6.1, -2.9, 1, 0, 0, 0, 0, 1)

        shrt = scale * shear * rot * trans
        shrtVal = (1.103009, 21.773710, 2.947696, 0,
                    0.000231, -0.000318, -0.000246, 0,
                    11.784681, 22.328316, -0.891846, 0,
                    -152.75, 19.6, 23.2, 1)
        self.assertAlmostMat(shrt, shrtVal)

        trhs = trans * rot * shear * scale
        trhsVal = (41.94635, -0.000192, 0.495749, 0,
                    66.280815, 0.000598, 2.327327, 0,
                    113.466327, 0.000754, 2.833334, 0,
                    -2475.782288, 0.058606, 35.623318, 1)
        self.assertAlmostMat(trhs, trhsVal)

        s = Vec(2, 13, -14)
        r = Vec(10, 20, 80)
        build = Mat().scale(s).rotate(r)
        mult = Mat() * Mat.Scale(s) * Mat.Rotate(r)
        self.assertEqual(build, mult)


    def TestTranspose(self):
        m = Mat(range(16))
        t = m.transpose()
        self.assertEqual(t.transpose(), m)
        self.assertEqual(m, Mat(m.rows()))
        self.assertEqual(t.rows(), m.columns())
        self.assertEqual(t.columns(), m.rows())
        self.assertEqual(t, (0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15))
        self.assertEqual(m.columns(), [[0, 4, 8, 12], [1, 5, 9, 13], [2, 6, 10, 14], [3, 7, 11, 15]])
        self.assertEqual(m.rows(), [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]])


    def TestDeterminant(self):
        ident = Mat()
        trans = Mat(1, 0, 0, 0,  0, 1, 0, 0,  0, 0, 1, 0,  -152.75, 19.6, 23.2, 1)
        rot = Mat(0.0501367536, 0.9897141042, 0.1339861854, 0.0, -0.7760466605, -0.0458413444, 0.6290072749, 0.0, 0.6286794785, -0.1355159145, 0.7657659892, 0.0, 0.0, 0.0, 0.0, 1.0)
        scale = Mat(22.0, 0, 0, 0,  0, -.00032, 0, 0,  0, 0, 3.7, 0,  0, 0, 0, 1)
        shear = Mat(1, 0, 0, 0, 1.05, 1, 0, 0, 6.1, -2.9, 1, 0, 0, 0, 0, 1)
        shrt = Mat(1.103009, 21.773710, 2.947696, 0, 0.000231, -0.000318, -0.000246, 0, 11.784681, 22.328316, -0.891846, 0, -152.75, 19.6, 23.2, 1)
        trhs = Mat(41.94635, -0.000192, 0.495749, 0, 66.280815, 0.000598, 2.327327, 0, 113.466327, 0.000754, 2.833334, 0, -2475.782288, 0.058606, 35.623318, 1)

        self.assertEqual(ident.determinant(), 1.0)
        self.assertAlmostEqual(trans.determinant(), 1.0)
        self.assertAlmostEqual(rot.determinant(), 1.0)
        self.assertAlmostEqual(shear.determinant(), 1.0)
        self.assertAlmostEqual(scale.determinant(), -0.026048)
        self.assertEqual(round(shrt.determinant(), 4), -0.026)
        self.assertEqual(round(trhs.determinant(), 4),  -0.026)

        # Also test that a singular returns 0
        badScale = Mat(1, 0, 0, 0,  0, 0, 0, 0,  0, 0, 1, 0,  0, 0, 0, 1)
        self.assertEqual(badScale.determinant(), 0.0)
        self.assertEqual(Mat(0).determinant(), 0.0)


    def TestInvert(self):
        class TestMat4(Mat):
            Epsilon = 1e-6

        minivec.PushEpsilon(1e-6)
        self.assertEqual(Mat(1.0).invert(), Mat(1.0))

        rot = Mat(0.0501367536, 0.9897141042, 0.1339861854, 0.0, -0.7760466605, -0.0458413444, 0.6290072749, 0.0, 0.6286794785, -0.1355159145, 0.7657659892, 0.0, 0.0, 0.0, 0.0, 1.0)
        invRot = (0.050137, -0.776047, 0.62868, 0.0, 0.989714, -0.045841, -0.135516, 0.0, 0.133986, 0.629007, 0.765766, 0.0, 0.0, 0.0, 0.0, 1.0)
        self.assertAlmostMat(TestMat4(rot.invert()), invRot)
        self.assertAlmostMat(TestMat4(rot.invert().invert()), rot)

        shrt = Mat(1.103009, 21.773710, 2.947696, 0, 0.000231, -0.000318, -0.000246, 0, 11.784681, 22.328316, -0.891846, 0, -152.75, 19.6, 23.2, 1)
        invShrt = (-0.222038, -3276.385729, 0.169861, 0.0, 0.103517, 1373.095291, -0.036604, 0.0, -0.342314, -8916.623602, 0.206821, 0.0, -28.003590, -320514.920263, 21.865433, 1.0)
        self.assertAlmostMat(TestMat4(shrt.invert()), invShrt)
        self.assertAlmostMat(TestMat4(shrt.invert().invert()), shrt)

        # Test that a singular returns False matrix
        badScale = Mat(1, 0, 0, 0,  0, 0, 0, 0,  0, 0, 1, 0,  0, 0, 0, 1)
        self.assertEqual(badScale.invert(), Mat(0.0))
        self.assertEqual(badScale.invert().invert(), Mat(0.0))

            
    def TestNonzero(self):
        "Truthness of matrices"
        self.assertTrue(Mat())
        self.assertFalse(Mat(0))
        self.assertFalse(Mat([0.0] * 16))
        self.assertTrue(Mat(1.0))
        self.assertFalse(Mat(1) * Mat(0))


    def TestEquality(self):
        "Equality and inequality"
        twos = [[0] * 4, [0] * 4, [0] * 4, [0] * 4]
        twos[0][0] = twos[1][1] = twos[2][2] = twos[3][3] = 2
        self.assertEqual(Mat(), Mat(1))
        self.assertEqual(Mat(1.0), Mat())
        self.assertEqual(Mat(twos), twos)
        self.assertEqual(Mat(2), twos)

        # Ensure hashing works for sets and dictionaries
        s = set((Mat(), Mat(1.0), Mat(range(16))))
        self.assertEqual(len(s), 2)
        self.assertTrue(Mat.Rotate(0, 0, 0) in s)
        self.assertFalse(Mat.Rotate(1, 2, 3) in s)
        s.add(Mat(range(10, 26)))
        s.difference_update([Mat(0), Mat(1)])
        self.assertEqual(len(s), 2)

        a = Mat(15)
        d = {a: True, Mat(15.0): True}
        self.assertTrue(a in d)
        self.assertTrue(Mat(15.0) in d)
        self.assertFalse(Mat() in d)
        d[Mat(-15)] = False
        self.assertEqual(len(d), 2)

        # Cannot compare with scalars
        self.assertRaises(TypeError, lambda: a == 4)
        self.assertRaises(TypeError, lambda: a > 2)


    def TestExact(self):
        "Precise equality, without epsilon"
        epsilon = minivec.Epsilon()
        a = Mat(5.0)
        self.assertFalse(a.exact(Mat(5.0 + 1e-12)))
        self.assertFalse(Mat(0).exact(Mat(epsilon)))
        self.assertFalse(a.exact(-a))


    def TestAlmost(self):
        "Almost equal, using epsilon"
        epsilon = minivec.Epsilon()
        a = Mat(5.0)
        self.assertTrue(a.almost(Mat(5.0 + 1e-12)))
        self.assertFalse(Mat(0).almost(Mat(epsilon)))
        self.assertFalse(a.almost(-a))

        # Test sloppy equality with epsilon
        self.assertTrue(a.almost(5.0 + 1e-12))
        self.assertFalse(a.almost(5.0 + 1e-2))

        b = Mat(12.0)
        minivec.PushEpsilon(1.0)
        self.assertTrue(b.almost(12.8))
        self.assertFalse(b.almost(13.4))

        
    def TestSequence(self):
        "Sequence style access"
        values = range(1, 17)
        a = Mat(values)

        self.assertEqual(a[0], 1.0)
        self.assertEqual(a[1], 2.0)
        self.assertEqual(a[2], 3.0)
        self.assertEqual(a[-1], 16.0)
        self.assertEqual(a[-2], 15.0)
        self.assertEqual(a[-3], 14.0)
        self.assertEqual([v for v in a], values)
        self.assertEqual(max(a), 16.0)
        self.assertEqual(any(a), True)
        self.assertEqual(a[:2], (1.0, 2.0))
        self.assertEqual(a[1:6], (2.0, 3.0, 4.0, 5.0, 6.0))
        self.assertEqual(a[20:], ())


    def TestSubclass(self):
        "Ensure the subclass is preserved"
        class SuperMat4(Mat):
            pass

        a = SuperMat4.Scale(1, 2, 3)
        b = SuperMat4.Rotate((2, 3, 4))
        c = Mat()
        self.assertTrue(a, SuperMat4)
        self.assertTrue(isinstance(a * b, SuperMat4))
        self.assertTrue(isinstance(a * c, SuperMat4))
        self.assertTrue(isinstance(a * 2, SuperMat4))


    def TestCompose(self):
        trans = Vec(.015, 1234, -71)
        scale = Vec(2, 31, 14)
        rot = Vec(11.1, 2.2, 31.0)
        shear = Vec(0.2, 0.0, 0.0)

        build = Mat().scale(scale).shear(shear).rotate(rot).translate(trans)
        mult = Mat() * Mat.Scale(scale) * Mat.Shear(shear) * Mat.Rotate(rot) * Mat.Translate(trans)
        self.assertEqual(build, mult)

        compose = Mat.Compose(trans, rot, shear, scale)
        self.assertEqual(build, compose)
        self.assertEqual(mult, compose)

        t, r, h, s = compose.decompose()
        self.assertEqual(trans, t)
        self.assertAlmostMat(rot, r)
        self.assertAlmostMat(shear, h)
        self.assertAlmostMat(scale, s)

        recompose = Mat.Compose(t, r, h, s)
        self.assertAlmostMat(compose, recompose)


    def TestLerp(self):
        ident = Mat()

        t = Vec(1, -2, 3)
        trans = Mat.Translate(t)
        self.assertEqual(ident.lerp(0.0, trans), ident)
        self.assertEqual(ident.lerp(1.0, trans), trans)
        self.assertEqual(ident.lerp(0.3, trans), Mat.Translate(t * 0.3))

        s = Vec(12, 13, 14)
        scale = Mat.Scale(s)
        self.assertEqual(ident.lerp(0.0, scale), ident)
        self.assertEqual(ident.lerp(1.0, scale), scale)
        l = ident.lerp(0.3, scale)
        ls = Vec(1).lerp(0.3, s)
        self.assertEqual(l, Mat.Scale(ls))

        r = Vec(90, 0, 0)
        rot = Mat.Rotate(r)
        self.assertEqual(ident.lerp(0.0, rot), ident)
        self.assertAlmostMat(ident.lerp(1.0, rot), rot)
        self.assertAlmostMat(ident.lerp(0.3, rot), Mat.Rotate(r * 0.3))

        r = Vec(0, -100, 0)
        rot = Mat.Rotate(r)
        self.assertEqual(ident.lerp(0.0, rot), ident)
        self.assertAlmostMat(ident.lerp(1.0, rot), rot)
        self.assertAlmostMat(ident.lerp(0.3, rot), Mat.Rotate(r * 0.3))

        r = Vec(0, 0, 70)
        rot = Mat.Rotate(r)
        self.assertEqual(ident.lerp(0.0, rot), ident)
        self.assertAlmostMat(ident.lerp(1.0, rot), rot)
        self.assertAlmostMat(ident.lerp(0.3, rot), Mat.Rotate(r * 0.3))


    def TestPerspective(self):
        pass  # TODO


    def assertAlmostMat(self, first, second):
        if not first.almost(second):
            raise self.failureException, \
                  ('%r !~= %r' % (first, second))

    

if __name__ == "__main__":
    unittest.main()
